package org.syx.droid.telnet.test;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import android.os.Handler;
import android.util.Log;

/**
 * Listens for incoming connections at a specified port. If {@link TelnetServer}
 * receives a connection, it will have a new {@link SocketWatcher} handle the
 * socket on a separate execution thread. It will then go back to listening for
 * incoming connections.
 * 
 * @author cesardepadua
 * 
 */
public class TelnetServer implements Runnable {
	/**
	 * A list of execution threads that {@link SocketWatcher}s run on.
	 */
	private List<Thread> threads;
	
	/**
	 * The {@link ServerSocket} that listens to a specified port for incoming
	 * connections.
	 */
	private ServerSocket server;
	
	/**
	 * A {@link Handler} to the UI thread to allow appending to the specified
	 * {@link TextView}.
	 */
	private Handler handler;
	
	/**
	 * The port to listen to.
	 */
	private final int port;
	
	private TelnetServer (Handler handler, int port) {
	
		this.threads = new ArrayList<Thread> ();
		this.server = null;
		this.port = port;
		this.handler = handler;
	}
	
	/**
	 * 
	 * @param h
	 *            A handler that is bound to the UI Thread
	 * @param p
	 *            The port to listen to.
	 * @return An instance of {@link TelnetServer}.
	 */
	public static TelnetServer getInstance (Handler h, int p) {
	
		return new TelnetServer (h, p);
	}
	
	/**
	 * Initializes the {@link ServerSocket}, binding it to the port by the
	 * class.
	 * 
	 * @return true if is successful, false if otherwise.
	 */
	private boolean setupServerSocket () {
	
		try {
			Log.i ("TelnetServer", "Creating a new ServerSocket");
			this.server = new ServerSocket (this.port);
			Log.i ("TelnetServer", "ServerSocket created!");
			
			return this.server.isBound ();
			
		} catch (IOException e) {
			Log.i ("TelnetProcessor", "Error: ", e);
			return false;
		}
	}
	
	/**
	 * Waits for an incoming connection. This method blocks until it receives a
	 * connection because of `accept ()`.
	 * 
	 * @return The {@link Socket} to the client that connected to the
	 *         {@ServerSocket}, if successful. `null` if there
	 *         was an error.
	 */
	private Socket waitForConnection () {
	
		try {
			Log.i ("TelnetServer", "Waiting for connections");
			Socket s = this.server.accept ();
			Log.i ("TelnetServer", "Connection Received!");
			
			return s;
			
		} catch (IOException e) {
			Log.i ("TelnetProcessor", "Error: ", e);
			return null;
		}
	}
	
	@Override
	public void run () {
	
		Log.i ("TelnetServer", "Starting up server!");
		
		if (this.setupServerSocket ()) {
			while (true) {
				Socket s = this.waitForConnection ();
				
				if (s != null) {
					SocketWatcher w = SocketWatcher.create (this.handler, s);
					
					Thread t = new Thread (w, "TelnetServerThread");
					t.start ();
					
					threads.add (t);
				}
			}
			
		} else {
			Log.i ("TelnetServer", "Failed to create server");
		}
	}
	
}
